import { createReducer } from '@reduxjs/toolkit'
import {
  addMulticallListeners,
  errorFetchingMulticallResults,
  fetchingMulticallResults,
  removeMulticallListeners,
  toCallKey,
  updateMulticallResults
} from './actions'

export interface MulticallState {
  callListeners?: {
    // on a per-chain basis
    [chainId: number]: {
      // stores for each call key the listeners' preferences
      [callKey: string]: {
        // stores how many listeners there are per each blocks per fetch preference
        [blocksPerFetch: number]: number
      }
    }
  }

  callResults: {
    [chainId: number]: {
      [callKey: string]: {
        data?: string | null
        blockNumber?: number
        fetchingBlockNumber?: number
      }
    }
  }
}

const initialState: MulticallState = {
  callResults: {}
}

export default createReducer( initialState, builder =>
  builder
    // 添加调用的监听
    .addCase( addMulticallListeners, ( state, { payload: { calls, chainId, options: { blocksPerFetch = 1 } = {} } } ) => {

      // 从旧的state中取出callListeners, 如果没有就设为空{}
      const listeners: MulticallState['callListeners'] = state.callListeners
        ? state.callListeners
        : ( state.callListeners = {} ) 
      
      // 取出当前链的调用结果表
      listeners[chainId] = listeners[chainId] ?? {}

      // 遍历调用
      calls.forEach( call => {
        // 先将调用系列化成callKey
        const callKey = toCallKey( call )
        listeners[chainId][callKey] = listeners[chainId][callKey] ?? {}
        // 更新遍历结果表中的blocksPerFetch
        listeners[chainId][callKey][blocksPerFetch] = ( listeners[chainId][callKey][blocksPerFetch] ?? 0 ) + 1
      } )

    } )
    // 移除调用的监听
    .addCase(
      removeMulticallListeners,
      ( state, { payload: { chainId, calls, options: { blocksPerFetch = 1 } = {} } } ) => {
        const listeners: MulticallState['callListeners'] = state.callListeners
          ? state.callListeners
          : ( state.callListeners = {} )

        if ( !listeners[chainId] ) return
        calls.forEach( call => {
          const callKey = toCallKey( call )
          if ( !listeners[chainId][callKey] ) return
          if ( !listeners[chainId][callKey][blocksPerFetch] ) return

          if ( listeners[chainId][callKey][blocksPerFetch] === 1 ) {
            delete listeners[chainId][callKey][blocksPerFetch]
          } else {
            listeners[chainId][callKey][blocksPerFetch]--
          }
        } )
      }
    )
    // 获取调用结果
    .addCase( fetchingMulticallResults, ( state, { payload: { chainId, fetchingBlockNumber, calls } } ) => {

      state.callResults[chainId] = state.callResults[chainId] ?? {}

      calls.forEach( call => {
        const callKey = toCallKey( call )
        const current = state.callResults[chainId][callKey]
        if ( !current ) {
          state.callResults[chainId][callKey] = {
            fetchingBlockNumber
          }
        } else {
          if ( ( current.fetchingBlockNumber ?? 0 ) >= fetchingBlockNumber ) return
          state.callResults[chainId][callKey].fetchingBlockNumber = fetchingBlockNumber
        }
      } )

      
    } )
    // 获取结果出错
    .addCase( errorFetchingMulticallResults, ( state, { payload: { fetchingBlockNumber, chainId, calls } } ) => {
      state.callResults[chainId] = state.callResults[chainId] ?? {}
      calls.forEach( call => {
        const callKey = toCallKey( call )
        const current = state.callResults[chainId][callKey]
        if ( !current ) return // only should be dispatched if we are already fetching
        if ( current.fetchingBlockNumber === fetchingBlockNumber ) {
          delete current.fetchingBlockNumber
          current.data = null
          current.blockNumber = fetchingBlockNumber
        }
      } )
    } )
    // 更新调用结果
    .addCase( updateMulticallResults, ( state, { payload: { chainId, results, blockNumber } } ) => {
      // 先取出当前链的调用结果表
      state.callResults[chainId] = state.callResults[chainId] ?? {}
      // 遍历输入的结果，存入当前链的调用结果表
      Object.keys( results ).forEach( callKey => {
        const current = state.callResults[chainId][callKey]
        // 先判断区块高度是否比旧的大
        if ( ( current?.blockNumber ?? 0 ) > blockNumber ) return
        // 然后更新callkey对应的调用结果
        state.callResults[chainId][callKey] = {
          data: results[callKey],
          blockNumber
        }
      } )
    } )
)
